import SerializeAble, { SerializeClass, Serialize, obj2n, n2c } from "./serialize";
import Utils, { Uid } from "./utils";
import { Library, LibraryRecord } from "./library";
import { Subject } from "./subject";
import { DeepClone, DeepEqual } from "./data_ext";

export function CreateUniObj<CT extends UniqueObjectConfig, TO extends UniqueObject<CT>>(objCtor: { prototype: TO }, configCtor: { prototype: CT }) {
    //@ts-ignore
    let conf = new configCtor();
    //@ts-ignore
    return <TO>UniqueObjectConfig.AssignUniqueObject(new objCtor(), conf);
}
export function CreateUniObjByKey<CT extends UniqueObjectConfig, TO extends UniqueObject<CT>>(key: string, objCtor: { prototype: TO }, configCtor: { prototype: CT }) {
    let conf = Library.GetRecord(configCtor, key);
    //@ts-ignore
    return <TO>UniqueObjectConfig.AssignUniqueObject(new objCtor(), conf);
};
@SerializeClass("UniqueObjectConfig")
export class UniqueObjectConfig extends LibraryRecord {
    // static CreateUniqueObject<CT extends UniqueObjectConfig>(config: CT): UniqueObject<CT> {
    //     let obj = new UniqueObject<CT>();
    //     this.AssignUniqueObject(obj, config);
    //     return obj;
    // }
    static AssignUniqueObject<CT extends UniqueObjectConfig, TA extends UniqueObject<CT>>(obj: TA, config: CT) {
        obj.init(
            Uid.getUid(20),
            config
        );
        return obj;
    }

};


type FunctionPropertyNames<T> = {
    [K in keyof T]: T[K] extends Function ? K : never
}[keyof T];
type NonFunctionProperties<T> = Omit<T, FunctionPropertyNames<T>>;
const UDE = ["$cn", "__cn", "key", "constructor", "toJSON", "assignFromJSON", "clone"];
export type UniDataExcept = "key" | "constructor";
//排除函数成员和UniDataExcept成员
export type UniData<T> = Omit<NonFunctionProperties<T>, UniDataExcept>;

@SerializeClass("UniqueObject")
export class UniqueObject<CT extends UniqueObjectConfig> extends SerializeAble {
    @Serialize()
    uuid: string = "";
    @Serialize()
    configKey: string = "";
    @Serialize()
    configClassName: string = "";

    config: CT = null;
    @Serialize()
    data: UniData<CT>;

    subject = new Subject();

    assignFromJSON(json: any): void {
        super.assignFromJSON(json);
        if (this.configKey) {
            this.config = Library.GetRecord(n2c(this.configClassName), this.configKey);
        }
        else {
            console.log(`${this.constructor["$cn"]} 类没有configKey,将创建一个默认配置。`);
            let ctor = n2c(this.configClassName);
            this.config =  new ctor();
        }
        this.data = CreateDataProxy(this.config, this.subject, UDE as (keyof CT)[]);
        Object.assign(this.data, json.data);
        this.onInit();
    }
    init<T extends CT>(uuid: string, config: T) {
        this.uuid = uuid;
        this.config = config;
        this.configKey = this.config.key;
        this.configClassName = obj2n(this.config);

        // Use the factory function to create a Proxy for the data
        this.data = CreateDataProxy(config, this.subject, UDE as (keyof T)[]);

        // Copy initial values from config to the Proxy target
        for (let ckey in config) {
            if (!UDE.includes(ckey)) {
                this.data[ckey as string] = DeepClone(config[ckey]);
            }
        }

        this.onInit();
    }

    onInit() {

    }
    //寻找 data 与 config 差异。并输出一个obj
    static FindDiff<CT extends UniqueObjectConfig, T extends UniqueObject<CT>>(record: T) {
        let diff = <any>{};
        const DataProp = Object.keys(record.data);
        const ConfigProp = Object.keys(record.config);
        let propList = [];
        for (let i = 0; i < DataProp.length; i++) {
            let propKey = DataProp[i];
            if (propKey === "key" || propKey === "fromJSON" || propKey === "toJSON") {
                continue;
            }
            if (-1 !== ConfigProp.findIndex(ele => ele == propKey)) {
                propList.push(propKey);
            }
        }
        propList.forEach(propKey => {
            if (!DeepEqual(record.data[propKey], record.config[propKey])) {
                diff[propKey] = record.data[propKey];
            }
        });
        return diff;
    }
};

export class UniqueObjectPropertyChanged {
    property = "";
    old: any;
    value: any;
};


function CreateDataProxy<T extends UniqueObjectConfig>(
    config: T,
    subject: Subject,
    exceptKeys: (keyof T)[]
): any {
    return new Proxy({}, {
        get: (target, property, receiver) => {
            if (property in target) {
                return Reflect.get(target, property, receiver);
            }
            if (property in config && !exceptKeys.includes(property as keyof T)) {
                return config[property as keyof T];
            }
            return undefined;
        },
        set: (target, property, value, receiver) => {
            if (exceptKeys.includes(property as keyof T)) {
                return true;
            }
            let old = DeepClone(target[property]);
            const success = Reflect.set(target, property, value, receiver);
            let msg = new UniqueObjectPropertyChanged();
            //@ts-ignore
            msg.property = property;
            msg.old = old;
            msg.value = value;
            subject.emit("changed", msg);
            return success;
        },
        deleteProperty: (target, property) => {
            if (exceptKeys.includes(property as keyof T)) {
                return false;
            }
            return Reflect.deleteProperty(target, property);
        },
        // Other traps can be added as needed
    });
}

export type UOC = UniqueObjectConfig;
export type UO<CT extends UniqueObjectConfig> = UniqueObject<CT>;

export const UOC = UniqueObjectConfig;
export const UO = UniqueObject;